package com.blog.aspect;

import com.blog.annotation.RequiredLog;
import com.blog.entity.Log;
import com.blog.entity.User;
import com.blog.mapper.LogMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * 记录用户行为日志的切面对象
 *
 * @author 黄嘉强
 * @Aspect注解描述的类为切面类型，此类型可以定义： 1）切入点 （要切入拓展业务逻辑的一些方法集合）
 * 2）通知方法 （拓展业务逻辑的实现）
 */
@Aspect
@Component
public class LogAspect {

    private static final Logger log= LoggerFactory.getLogger(LogAspect.class);

    /**
     * 通过@PointCut定义切入点，@annotation是切入点表达式的一种写法，
     * 这里表示基于@RequiredLog注解描述的方法为切入点方法。
     * 这个方法内部不用写任何代码，此方法只负责承载注解的定义。
     */
    @Pointcut("@annotation(com.blog.annotation.RequiredLog)")
    public void doLog(){}


    /**
     * 通过@Around注解描述功能拓展方法，表示我们在此方法内部可以为目标业务进行功能拓展，
     * 可以在目标执行之前，也可以在目标执行之后。
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("doLog()")
    public Object doLogAround(ProceedingJoinPoint joinPoint)throws Throwable{
        int status=1;
        String error="";
        long time=-1;
        long t1=System.currentTimeMillis();
        try {
            Object result = joinPoint.proceed();
            long t2 = System.currentTimeMillis();
            time = t2 - t1;
            return result;
        }catch (Throwable e){
            e.printStackTrace();
            long t3=System.currentTimeMillis();
            time=t3-t1;
            status=0;
            error=e.getMessage();
            throw e;
        }finally{
            saveUserLog(joinPoint,time,status,error);
        }
    }

    @Autowired
    private LogMapper logMapper;

    /**
     * 将获取到的日志信息保存到数据库表中
     * @param joinPoint
     * @param time
     * @param status
     * @param error
     * @throws NoSuchMethodException
     * @throws JsonProcessingException
     */
    private void saveUserLog(ProceedingJoinPoint joinPoint,long time,int status,String error)
            throws NoSuchMethodException, JsonProcessingException {
        //1.获取用户行为日志
        ServletRequestAttributes requestAttributes=
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String ip=request.getRemoteAddr();
        User user=(User)request.getSession().getAttribute("user");
        String username="user";
        if(user!=null) {username=user.getUsername();}
        Class<?> targetCls = joinPoint.getTarget().getClass();
        MethodSignature ms = (MethodSignature) joinPoint.getSignature();
        Method targetMethod = targetCls.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
        RequiredLog requiredLog = targetMethod.getAnnotation(RequiredLog.class);
        String operation= requiredLog.operation();
        String method=targetCls.getName()+"."+targetMethod.getName();
        String params=new ObjectMapper().writeValueAsString(joinPoint.getArgs());
        //2.封装用户行为日志
        Log userLog=new Log();
        userLog.setIp(ip);
        userLog.setUsername(username);
        userLog.setOperation(operation);
        userLog.setCreatedTime(new Date());
        userLog.setMethod(method);
        userLog.setParams(params);
        userLog.setTime(time);
        userLog.setStatus(status);
        userLog.setError(error);
        //3.输出并存储用户行为日志
        log.info("user log is {}",userLog);
        logMapper.insert(userLog);
    }
}
